package com.tianyu.inspection.service.websocket;

import com.alibaba.fastjson2.JSONException;
import com.alibaba.fastjson2.JSONObject;
import com.tianyu.inspection.common.component.AppConstants;
import com.tianyu.inspection.common.enums.RoState;
import com.tianyu.inspection.common.enums.WebSocketMessageType;
import com.tianyu.inspection.entity.BasicInformation;
import com.tianyu.inspection.entity.User;
import com.tianyu.inspection.entity.model.WebSocketChannelMo;
import com.tianyu.inspection.entity.vo.WebSocketRo;
import com.tianyu.inspection.service.api.IBasicInformationService;
import com.tianyu.inspection.service.api.IUploadFileService;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.websocketx.PingWebSocketFrame;
import io.netty.handler.codec.http.websocketx.PongWebSocketFrame;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;
import io.netty.util.AttributeKey;
import io.ocs.model.OcsSimpleModel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.net.InetSocketAddress;
import java.util.Map;
import java.util.Objects;

import static com.tianyu.inspection.service.websocket.server.WebSocketServer.WEB_SOCKET_CHANNEL_MAP;

/**********************************
 * @Author YSW
 * @Description
 * @Date 2023/10/31 - 11:08
 **********************************/

@Slf4j
@Component
@ChannelHandler.Sharable
public class WebSocketHandler extends SimpleChannelInboundHandler<Object> {

    @Resource
    private HashOperations<String, String, Object> hashOperations;

    @Resource
    private ValueOperations<String, Object> valueOperations;

    @Resource
    private AppConstants appConstants;

    @Resource
    private IUploadFileService uploadFileService;

    @Resource
    private IBasicInformationService basicInformationService;

    @Resource
    private WebSocketService webSocketService;

    private final AttributeKey<String> channelUserNickName = AttributeKey.valueOf("nickName");

    private final AttributeKey<String> channelUserPhone = AttributeKey.valueOf("phone");

    private final AttributeKey<Integer> channelRoleId = AttributeKey.valueOf("roleId");

    private final AttributeKey<Integer> channelUserId = AttributeKey.valueOf("userId");

    private final AttributeKey<String> channelUserName = AttributeKey.valueOf("userName");

    private final AttributeKey<Integer> channelProjectId = AttributeKey.valueOf("projectId");

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, Object frame) throws Exception {
        // 判断是否ping消息
        if (frame instanceof PingWebSocketFrame) {
            ctx.channel().write(new PongWebSocketFrame(((PingWebSocketFrame) frame).content().retain()));
            return;
        }
        if (frame instanceof TextWebSocketFrame) {
            String frameData = ((TextWebSocketFrame) frame).text();
            JSONObject obj;
            try {
                obj = JSONObject.parseObject(frameData);
            } catch (JSONException e) {
                log.debug("接收到数据非JSON字符串！");
                return;
            }
            int type;
            Integer type1 = obj.getInteger("type");
            if (Objects.isNull(type1)) {
                log.debug("缺少type字段！");
                return;
            } else {
                type = type1;
            }
            if (type == 1) {
                register(ctx.channel(), obj);
            }
            if (type == 2) {
                heartBeat(ctx.channel(), obj);
            }
            if (type == 3) {
                userPosition(ctx.channel(), obj);
            }
            if (type == 4) {
                gisModelUploadProcess(ctx.channel(), obj);
            }
        }
    }

    public void register(Channel channel, JSONObject obj) {
        WebSocketRo<Map<String, Object>> webSocketRo = new WebSocketRo<>();
        webSocketRo.setMessageType(WebSocketMessageType.WS_REGISTER.getValue());
        Integer userId = obj.getInteger("userId");
        String requestToken = obj.getString("token");
        if (Objects.isNull(requestToken) || Objects.isNull(userId)) {
            webSocketRo.setState(RoState.WEBSOCKET_REGISTER_PARAM_ERROR.getValue());
            webSocketRo.setMessage(RoState.WEBSOCKET_REGISTER_PARAM_ERROR.getMessage());
            String replyMessage = JSONObject.toJSONString(webSocketRo);
            channel.writeAndFlush(new TextWebSocketFrame(replyMessage));
            channel.close();
            return;
        }
        User loginUser = (User) valueOperations.get(appConstants.redisUserKey + userId);
        if (Objects.nonNull(loginUser)) {
            String phone = loginUser.getPhone();
            String nickName = loginUser.getNickName();
            String userName = loginUser.getUserName();
            Integer projectId = loginUser.getProjectId();
            String redisToken = loginUser.getToken();
            Integer roleId = loginUser.getRoleId();
            //Token错误
            if (!requestToken.equals(redisToken)) {
                webSocketRo.setState(RoState.WEBSOCKET_REGISTER_TOKEN_ERROR.getValue());
                webSocketRo.setMessage(RoState.WEBSOCKET_REGISTER_TOKEN_ERROR.getMessage());
                String replyMessage = JSONObject.toJSONString(webSocketRo);
                channel.writeAndFlush(new TextWebSocketFrame(replyMessage));
                channel.close();
            } else {
                WebSocketChannelMo webSocketChannelMo = WEB_SOCKET_CHANNEL_MAP.get(userId);
                //该用户WebSocket已在线
                if (Objects.nonNull(webSocketChannelMo) && webSocketChannelMo.getChannel().isActive()) {
                    final Channel oldChannel = webSocketChannelMo.getChannel();
                    //重复发送注册包
                    if (oldChannel.id().equals(channel.id())) {
                        webSocketRo.setState(RoState.WEBSOCKET_REPEAT_REGISTRATION_ERROR.getValue());
                        webSocketRo.setMessage(RoState.WEBSOCKET_REPEAT_REGISTRATION_ERROR.getMessage());
                        String replyMessage = JSONObject.toJSONString(webSocketRo);
                        channel.writeAndFlush(new TextWebSocketFrame(replyMessage));
                        InetSocketAddress ipSocket = (InetSocketAddress) channel.remoteAddress();
                        String clientIp = ipSocket.getAddress().getHostAddress();
                        log.debug("WebSocketChannel重复注册 -> channelID[{}], 用户[{}], 客户端IP[{}], 端口[{}]", channel.id(), userId, clientIp, ipSocket.getPort());
                        return;
                        //异处已登录, 关闭连接
                    } else {
                        oldChannel.close();
                        InetSocketAddress ipSocket = (InetSocketAddress) oldChannel.remoteAddress();
                        String clientIp = ipSocket.getAddress().getHostAddress();
                        log.debug("关闭重复WebSocketChannel -> channelID[{}], 用户[{}], 客户端IP[{}], 端口[{}]", oldChannel.id(), userId, clientIp, ipSocket.getPort());
                        WEB_SOCKET_CHANNEL_MAP.remove(userId);
                    }
                }
                if (Objects.isNull(webSocketChannelMo)) {
                    webSocketChannelMo = new WebSocketChannelMo();
                }
                channel.attr(channelUserNickName).set(nickName);
                channel.attr(channelUserPhone).set(phone);
                channel.attr(channelRoleId).set(roleId);
                channel.attr(channelUserId).set(userId);
                channel.attr(channelUserName).set(userName);
                channel.attr(channelProjectId).set(projectId);
                webSocketChannelMo.setChannel(channel).setChannelId(channel.id()).setToken(requestToken).setUserId(userId).setProjectId(projectId);
                webSocketChannelMo.setRoleId(roleId);
                WEB_SOCKET_CHANNEL_MAP.put(userId, webSocketChannelMo);
                webSocketRo.setState(RoState.WEBSOCKET_SUCCESS.getValue());
                webSocketRo.setMessage("WebSocket注册成功");
                InetSocketAddress newIpSocket = (InetSocketAddress) channel.remoteAddress();
                String replyMessage = JSONObject.toJSONString(webSocketRo);
                channel.writeAndFlush(new TextWebSocketFrame(replyMessage));
                if (roleId > 1) {
                    final JSONObject jsonObject = new JSONObject();
                    jsonObject.put("online", 1);
                    jsonObject.put("roleId", roleId);
                    jsonObject.put("userId", userId);
                    jsonObject.put("userName", userName);
                    jsonObject.put("projectId", projectId);
                    webSocketService.sendAdminMessage(WebSocketMessageType.WS_ADMIN_USER, jsonObject);
                }
                log.info("项目[{}] - 用户[{}]-[{}], WebSocket[{}] 注册成功 IP[{}], 端口[{}]", projectId, userId, userName, channel.id(), newIpSocket.getAddress().getHostAddress(), newIpSocket.getPort());
            }
        } else {
            webSocketRo.setState(RoState.WEBSOCKET_FAIL.getValue());
            webSocketRo.setMessage("未查询到用户登录信息, WebSocket注册失败");
            String replyMessage = JSONObject.toJSONString(webSocketRo);
            channel.writeAndFlush(new TextWebSocketFrame(replyMessage));
            channel.close();
        }
    }

    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        if (evt instanceof IdleStateEvent) {
            IdleState state = ((IdleStateEvent) evt).state();
            if (state == IdleState.READER_IDLE) {
                Integer userId = ctx.channel().attr(channelUserId).get();
                ctx.channel().close();
                if (Objects.nonNull(userId)) {
                    WEB_SOCKET_CHANNEL_MAP.remove(userId);
                    log.debug("已注册WebSocket[{}] 用户[{}] 30秒未发送心跳包,已断开连接! ", ctx.channel().id(), userId);
                } else {
                    log.debug("未注册WebSocket[{}] 30秒未发送心跳包, 已断开连接, 移出推送遥测队列!", ctx.channel().id());
                }
            }
        } else {
            super.userEventTriggered(ctx, evt);
        }
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        Integer roleId = ctx.channel().attr(channelRoleId).get();
        Integer userId = ctx.channel().attr(channelUserId).get();
        String userName = ctx.channel().attr(channelUserName).get();
        Integer projectId = ctx.channel().attr(channelProjectId).get();
        if (Objects.nonNull(userId)) {
            if (roleId > 1) {
                final JSONObject jsonObject = new JSONObject();
                jsonObject.put("online", 0);
                jsonObject.put("roleId", roleId);
                jsonObject.put("userId", userId);
                jsonObject.put("userName", userName);
                jsonObject.put("projectId", projectId);
                jsonObject.put("message", "断开连接! ");
                webSocketService.sendAdminMessage(WebSocketMessageType.WS_ADMIN_USER, jsonObject);
            }
            log.info("项目[{}] - 用户[{}]-[{}],  WebSocket[{}] 断开连接! ", projectId, userId, userName, ctx.channel().id());
            if (Objects.nonNull(WEB_SOCKET_CHANNEL_MAP.get(userId)) &&
                    Objects.nonNull(WEB_SOCKET_CHANNEL_MAP.get(userId).getChannel()) &&
                    WEB_SOCKET_CHANNEL_MAP.get(userId).getChannel().id().equals(ctx.channel().id())) {
                WEB_SOCKET_CHANNEL_MAP.remove(userId);
            }
        }
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        Integer roleId = ctx.channel().attr(channelRoleId).get();
        Integer userId = ctx.channel().attr(channelUserId).get();
        String userName = ctx.channel().attr(channelUserName).get();
        Integer projectId = ctx.channel().attr(channelProjectId).get();
        if ("Connection reset by peer".equals(cause.getMessage())) {
            if (roleId > 1) {
                final JSONObject jsonObject = new JSONObject();
                jsonObject.put("online", 0);
                jsonObject.put("roleId", roleId);
                jsonObject.put("userId", userId);
                jsonObject.put("userName", userName);
                jsonObject.put("projectId", projectId);
                jsonObject.put("message", "客户端强制关闭连接! ");
                webSocketService.sendAdminMessage(WebSocketMessageType.WS_ADMIN_USER, jsonObject);
            }
            log.error("项目[{}] - 用户[{}]-[{}],  WebSocket[{}] 客户端强制关闭连接! ", projectId, userId, userName, ctx.channel().id());
        } else {
            log.error(cause.getMessage());
        }
    }

    public void heartBeat(Channel channel, JSONObject obj) {
        WebSocketRo<Void> webSocketRo = new WebSocketRo<>();
        webSocketRo.setMessageType(WebSocketMessageType.WS_HEARTBEAT.getValue());
        Integer userId = obj.getInteger("userId");
        if (Objects.isNull(userId)) {
            webSocketRo.setState(RoState.WEBSOCKET_HEARTBEAT_PARAM_ERROR.getValue());
            webSocketRo.setMessage(RoState.WEBSOCKET_HEARTBEAT_PARAM_ERROR.getMessage());
            String replyMessage = JSONObject.toJSONString(webSocketRo);
            channel.writeAndFlush(new TextWebSocketFrame(replyMessage));
            channel.close();
            return;
        }
        WebSocketChannelMo webSocketChannelMo = WEB_SOCKET_CHANNEL_MAP.get(userId);
        if (Objects.nonNull(webSocketChannelMo)) {
            webSocketRo.setState(RoState.WEBSOCKET_SUCCESS.getValue());
            webSocketRo.setMessage("心跳回复");
            String replyMessage = JSONObject.toJSONString(webSocketRo);
            channel.writeAndFlush(new TextWebSocketFrame(replyMessage));
            log.debug("用户[{}]WebSocket[{}] 发送心跳包! ", webSocketChannelMo.getUserId(), channel.id());
        } else {
            webSocketRo.setState(RoState.WEBSOCKET_FAIL.getValue());
            webSocketRo.setMessage("心跳发送失败, WebSocket未注册！");
            String replyMessage = JSONObject.toJSONString(webSocketRo);
            channel.writeAndFlush(new TextWebSocketFrame(replyMessage));
            channel.close();
            log.debug("未注册的WebSocket[{}] 发送心跳包, 连接已关闭! ", channel.id());
        }
    }

    public void userPosition(Channel channel, JSONObject obj) {
        final String userNickName = channel.attr(channelUserNickName).get();
        final String userPhone = channel.attr(channelUserPhone).get();
        Integer userId = channel.attr(channelUserId).get();
        String userName = channel.attr(channelUserName).get();
        Integer projectId = channel.attr(channelProjectId).get();
        if (Objects.nonNull(userId)) {
            obj.put("userId", userId);
            obj.put("userName", userName);
            final JSONObject positionMessage = new JSONObject()
                    .fluentPut("userNickName", userNickName)
                    .fluentPut("userPhone", userPhone)
                    .fluentPut("userId", userId)
                    .fluentPut("userName", userName)
                    .fluentPut("position", obj.getJSONObject("content"));
            webSocketService.sendUserPositionMessage(positionMessage, projectId);
        }
    }

    @Resource
    private OcsSimpleModel ocsSimpleModel;

    @Resource
    public RedisTemplate<String, Object> redisTemplate;

    public void gisModelUploadProcess(Channel channel, JSONObject obj) {
        final String userNickName = channel.attr(channelUserNickName).get();
        final String userPhone = channel.attr(channelUserPhone).get();
        Integer userId = channel.attr(channelUserId).get();
        String userName = channel.attr(channelUserName).get();
        Integer projectId = channel.attr(channelProjectId).get();
        final JSONObject progress = obj.getJSONObject("content");
        final String modelId = progress.getString("modelId");
        final String finishPath = progress.getString("finishPath");
        final Integer objSize = progress.getInteger("objSize");
        final Object o = hashOperations.get(appConstants.redisOcsGroupPut + modelId, finishPath);
        if (o != null) {
            final JSONObject jsonObject = JSONObject.from(o);
            final String objPath = jsonObject.getString("objPath");
            final boolean exist = ocsSimpleModel.isObjectExist(objPath);
            if (!exist) {
                log.error("modelId[{}] objPath[{}] 不存在, 信息完成上报错误...", modelId, objPath);
                return;
            }
            log.info("modelId[{}] objPath[{}] size[{}] upload finished!!!", modelId, finishPath, objSize);
            jsonObject.put("isFinished", true);
            jsonObject.put("objSize", objSize);
            hashOperations.put(appConstants.redisOcsGroupPut + modelId, finishPath, jsonObject);
            final JSONObject ocsPutGroupProgress = uploadFileService.ocsPutGroupProgress(modelId);
            final Integer basicInformationId = ocsPutGroupProgress.getInteger("basicInformationId");
            final Integer objCount = ocsPutGroupProgress.getInteger("objCount");
            final Integer objUploadedCount = ocsPutGroupProgress.getInteger("objUploadedCount");
            final Integer totalSize = ocsPutGroupProgress.getInteger("totalSize");
            if (objUploadedCount % 10 == 0 || Objects.equals(objCount, objUploadedCount)) {
                final BasicInformation basicInformation = basicInformationService.selectById(basicInformationId);
                if (basicInformation != null) {
                    basicInformation.setJsonContent(JSONObject.from(basicInformation.getJsonContent())
                            .fluentPut("objUploadedCount", objUploadedCount)
                            .fluentPut("objUploadedSize", totalSize));
                    basicInformationService.updateById(basicInformation);
                    log.info("modelId[{}] objUploadedCount update db!!!", modelId);
                }
                if (objCount > 0 && Objects.equals(objCount, objUploadedCount)) {
                    redisTemplate.delete(appConstants.redisOcsGroupPut + modelId);
                    redisTemplate.delete(appConstants.redisOcsGroupPutProgress + modelId);
                    log.info("modelId[{}] size[{}] upload finished!!!", modelId,totalSize);
                }
            }
        }
    }
}
